package bhoost.jsi;

import java.util.Hashtable;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Semaphore;

public class EventManager { //extends JSIObject {
  public interface Handler {
    void onEvent();
  }
//  private Object                         eventPopObj = new Object();
//  private Object                         startReceiving = new Object();
  static  LinkedBlockingQueue<Event>     eventQueue;
  private Hashtable<Event.Type, Boolean> subscriptions;
  public  Event.Type                     eventType;
  private Handler                        handler;

  @SuppressWarnings("unused")
  private static XmppEvent           jsiXmppEvent;
  @SuppressWarnings("unused")
  private static BrowserHistoryEvent jsiHistEvent;
  @SuppressWarnings("unused")
  private static KeyEvent            jsiKeyEvent;
  @SuppressWarnings("unused")
  private static LocationEvent       jsiLocEvent;
  @SuppressWarnings("unused")
  private static CameraEvent         jsiCamEvent;

  private Hashtable<Event.Type, Event> typeToEvent;
  private static EventQueueThread    eventQThread;
  private Semaphore                  startSemaphore;
  private Semaphore                  readySemaphore;

  public void logD(String logMessage) {
    System.out.println(logMessage);
  }

  private static EventManager instance;

  public static EventManager getInstance() {
    if (instance == null)
      instance = new EventManager();
    return instance;
  }

  public void setHandler(Handler handler) {
    this.handler = handler;
  }

  private EventManager() {
    eventQueue = new LinkedBlockingQueue<Event>();
    subscriptions = new Hashtable<Event.Type, Boolean>();
    jsiXmppEvent = new XmppEvent();
    jsiHistEvent = new BrowserHistoryEvent();
    jsiKeyEvent = new KeyEvent();
    jsiLocEvent = new LocationEvent();
    jsiCamEvent = new CameraEvent();
    typeToEvent = new Hashtable<Event.Type, Event>(100);
    typeToEvent.put(Event.Type.xmpp, jsiXmppEvent);
    typeToEvent.put(Event.Type.browserHistory, jsiHistEvent);
    typeToEvent.put(Event.Type.key, jsiKeyEvent);
    typeToEvent.put(Event.Type.geoLocation, jsiLocEvent);
    typeToEvent.put(Event.Type.camera, jsiCamEvent);
    startSemaphore = new Semaphore(1);
    startSemaphore.acquireUninterruptibly();
    readySemaphore = new Semaphore(1);
  }

  public Event getEventInstance(String eventTypeStr) {
    if (eventTypeStr == null)
      return null;
    eventTypeStr = eventTypeStr.toLowerCase().trim();
    for (Event.Type type: Event.Type.values()) {
      if (type.toString().equals(eventTypeStr)) {
        return typeToEvent.get(type);
      }
    }
    return null;
  }

  public void queueEvent(Event event) {
    if (event != null && isSubscribed(event.getType())) {
      eventQueue.add(event);
    }
  }

  public boolean isSubscribed(Event.Type type) {
    return (subscriptions != null && subscriptions.containsKey(type) && subscriptions.get(type).booleanValue());
  }

  /**
   * events can be sent via a native even delivered to an event handler in JS or via
   * timer-based polling in JS
   * @return
   */
  public boolean isEventsViaTimer() {
    return false;
  }

  public void subscribe(String eventType) {
    logD("subscribing to event type: " + eventType);
    for (Event.Type type: Event.Type.values()) {
      if (type.toString().equals(eventType)) {
        logD("subscribed to event type: " + type.toString());
        subscriptions.put(type, true);

        if (type == Event.Type.geoLocation) {
//gv          startReceivingLocUpdates();
//          startSendingLocUpdates();
        }
        else if (type == Event.Type.xmpp) {
          Xmpp.doSend(true); // connects
        }
      }
    }
  }

  public void unsubscribe(String eventType) {
    logD("ubsubscribing from event type: " + eventType.toString());
    for (Event.Type type: Event.Type.values()) {
      if (type.toString().equals(eventType)) {
        logD("unsubscribed from event type: " + type.toString());
        subscriptions.put(type, false);

        if (type == Event.Type.geoLocation) {
//gv          stopReceivingLocationUpdates();
//          stopSendingLocationUpdates();
        }
        if (type == Event.Type.xmpp) {
          Xmpp.doSend(false);
        }
      }
    }
  }

  public void readyForNextEvent() {
    readySemaphore.release();
  }

  /**
   * @return true if pop was successful, false otherwise
   */
  public boolean popEvent() {
    if (!isEventsViaTimer())
      return false;

    if (eventQueue.isEmpty())
      return false;

    Event event = null;
    try {
      event = eventQueue.take();
    } catch (InterruptedException i) {
      logD("event queue thread interrupted1: " + i.getMessage());
    }

    eventType = event.eventType;
    copyEvent(event);
    return true;
  }

  public boolean hasEvent() {
    if (!isEventsViaTimer())
      return true;

    if (eventQueue == null)
      return false;

    synchronized(eventQueue) {
      return (!eventQueue.isEmpty());
    }
  }

  public void readyForEvents() {
    if (isEventsViaTimer())
      return;

    eventQueue = eventQueue == null ? new LinkedBlockingQueue<Event>() : eventQueue;
    eventQThread = new EventQueueThread();
    eventQThread.start();
    startSemaphore.release();

//    synchronized (startReceiving) {
//      startReceiving.notifyAll();
//    }
//    synchronized (eventPopObj) {
//      eventPopObj.notifyAll();
//    }
  }

  public String getEventType() {
    if (eventType != null)
      return eventType.toString();
    return null;
  }

  private void copyEvent(Event event) {
    logD("copying event");
    eventType = event.eventType;
    switch (eventType) {
      case xmpp:
        jsiXmppEvent.copyFrom((XmppEvent)event);
        return;
        /*
      case browserHistory:
        jsiHistEvent.copyFrom((BrowserHistoryEvent)event);
        return;
      case key:
        return jsiKeyEvent = ((KeyEvent)event).clone();
      case geoLocation:
        return jsiLocEvent = ((LocationEvent)event).clone();
      case camera:
        return jsiCamEvent = ((CameraEvent)event).clone();
        */
      default:
        return;
    }
  }

  public void destroy() {
    if (eventQThread != null)
      eventQThread.mustStop();
    if (isSubscribed(Event.Type.xmpp))
      Xmpp.getInstance().disconnect();
    instance = null;
  }

  class EventQueueThread extends Thread {
    private boolean handleEvents = true;

    public void mustStop() {
      handleEvents = false;
    }

    public void finish() {
      startSemaphore.release();
      readySemaphore.release();
      eventQueue.clear();
    }

    public void run() {
      startSemaphore.acquireUninterruptibly();

      while (handleEvents) {
        Event event = null;
        try {
          logD("waiting for pop, queue empty: " + eventQueue.isEmpty());
          event = eventQueue.take();
          logD("popped");
        } catch (InterruptedException i) {
          logD("event queue thread interrupted2: " + i.getMessage());
          if (!handleEvents)
            break;
          else
            continue;
        }

        if (!handleEvents) {
          logD("stopping event queue thread1");
          break;
        }

        copyEvent(event);

        logD("simulating key event");
        // wait for readyForNextEvent to be called
        readySemaphore.acquireUninterruptibly();
        handler.onEvent();
//        readySemaphore.release();
      }

      finish();
    }
  }
}